/*
 * Copyright Hundsun Technologies Inc. All Rights Reserved.
 */

package miner

import (
	"context"
	"fmt"
	"sync"

	"github.com/golang/protobuf/proto"
	"go.uber.org/atomic"
	commoncfg "hundsun.com/hsl/hschain/common/config"
	"hundsun.com/hsl/hschain/common/keystore"
	"hundsun.com/hsl/hschain/common/log/logging"
	"hundsun.com/hsl/hschain/common/util/pb"
	"hundsun.com/hsl/hschain/consensus/common"
	"hundsun.com/hsl/hschain/module/common/utils"
	"hundsun.com/hsl/hschain/protos"
	"hundsun.com/hsl/hschain/protos/consensus/raft"
)

const (
	configParamMinerAddress = "minerAddress"

	//configParamBlockProducingInterval 同一时间片内生产一个区块后等待多久再生产下一个区块（单位：ms）
	configParamBlockProducingInterval = "producingInterval"

	defaultProducingInterval = 100

	//configParamMinPendingTxFetchInterval 出块时，从交易缓冲池中获取交易的重试时间间隔（找不到时）
	configParamMinPendingTxFetchInterval = "minPendingTxFetchInterval"
	defaultMinPendingTxFetchInterval     = 100

	//configParamMinCheckBlockExecResultFetchInterval 出块时，获取验证块执行结果重试时间间隔（找不到时）
	configParamMinCheckBlockExecResultFetchInterval = "minCheckBlockExecResultFetchInterval"
	defaultMinCheckBlockExecResultFetchInterval     = 100

	logFieldMiner = "miner"
)

// NewMiner 新建一个Miner模块
func NewMiner(client *common.BaseClient) *RaftMiner {
	m := &RaftMiner{
		baseclient: client,
	}

	return m
}

// RaftMiner raft miner
type RaftMiner struct {
	//矿工帐号：用于对区块和区块背书结果进行签名
	MinerAccount keystore.Account

	baseclient *common.BaseClient

	producer *Producer

	cm *ConsensusModule

	ShutdownWg sync.WaitGroup

	Log logging.Log
}

// Start 启动
func (m *RaftMiner) Start(ctx context.Context, cfg *commoncfg.ModuleConfig, log logging.Log) error {
	m.Log = log

	minersInGenesisBlock := GetGenesisMiners()
	minerAddress := utils.GetStringConfigParameterValue(cfg, configParamMinerAddress, "")

	if len(minerAddress) == 0 {
		return fmt.Errorf("address of miner not configured")
	}
	ks := keystore.GetKeyStore()
	m.MinerAccount = ks.GetAccount(protos.Address(minerAddress))
	if m.MinerAccount == nil {
		return fmt.Errorf("account '%s' not found in keystore", minerAddress)
	}

	m.Log = m.Log.WithField(logFieldMiner, protos.Address(minerAddress).GetShortString())

	consensusModuleCtx, _ := context.WithCancel(ctx)
	var id int64
	peerIds := make([]int64, 0)
	for index, currentMiner := range minersInGenesisBlock {
		if currentMiner.Address == minerAddress {
			id = int64(index)
		} else {
			peerIds = append(peerIds, int64(index))
		}
	}

	consensusModule := NewConsensusModule(consensusModuleCtx, id, peerIds, log)
	m.cm = consensusModule

	clients := make([]*RaftConsensusClient, len(minersInGenesisBlock))
	for index, currentMiner := range minersInGenesisBlock {
		if currentMiner.Address != minerAddress {
			client, err := NewRatfConsensusClient(m, currentMiner)
			if err != nil {
				return err
			}
			clients[index] = client
		}
	}
	m.cm.clients = clients

	//raftConsensusServer := NewRaftConsensusServer(m)
	//err = raftConsensusServer.Start()
	//if err != nil {
	//	return err
	//}

	consensusModule.Start()
	// 初始化区块生产者
	producerCtx, _ := context.WithCancel(ctx)
	producingInterval := utils.GetIntConfigParameterValue(cfg, configParamBlockProducingInterval, defaultProducingInterval)
	pendingTxsRetryInterval := utils.GetIntConfigParameterValue(cfg, configParamMinPendingTxFetchInterval, defaultMinPendingTxFetchInterval)
	blockExecResultRetryInterval := utils.GetIntConfigParameterValue(cfg, configParamMinCheckBlockExecResultFetchInterval, defaultMinCheckBlockExecResultFetchInterval)

	m.producer = &Producer{
		module:      m,
		ctx:         producerCtx,
		isProducing: atomic.NewBool(false),
	}
	err := m.producer.init(m.MinerAccount, producingInterval, pendingTxsRetryInterval, blockExecResultRetryInterval)
	if err != nil {
		return err
	}

	m.ProduceBlocks()
	m.ShutdownWg.Add(1)
	return nil
}

// ProduceBlocks 生产块
func (m *RaftMiner) ProduceBlocks() {
	go m.producer.Run()
}

// ModRegValueExtraKeyMinerAddress ModRegValue的Extra字段的key--miner地址
const ModRegValueExtraKeyMinerAddress = "minerAddress"

// IsBlockSynchronized 成为leader出块前必须先进行同步检查，每个miner的一次leader期间只会检查一次，所以没有double check
func (m *RaftMiner) IsBlockSynchronized() bool {
	synchronized := m.baseclient.IsSynched()
	m.baseclient.SetBlockSynchronized(synchronized)
	if synchronized {
		m.Log.Info("Block Synchronized")
	}
	return synchronized
}

// GetGenesisMiners 获取创世块中配置的miners
func GetGenesisMiners() []*raft.Miner {
	genesisTxPayload := pb.GetGenesisTransactionPayload()

	if genesisTxPayload.Consensus.Type != raft.ConsensusType {
		panic("unexpected consensus type: " + genesisTxPayload.Consensus.Type)
	}
	var consensusConfPayload raft.ConsensusConfigPayload
	err := proto.Unmarshal(genesisTxPayload.Consensus.Payload, &consensusConfPayload)
	if err != nil {
		panic(err)
	}
	miners := consensusConfPayload.DefaultMiners
	return miners
}
